// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
// This source file is part of the Cangjie project, licensed under Apache-2.0
// with Runtime Library Exception.
//
// See https://cangjie-lang.cn/pages/LICENSE for license information.

#include <fstream>
#include <vector>
#include <iostream>

#include "cangjie/AST/Symbol.h"

#include "NodeCast.h"
#include "Util/Io.h"
#include "Rules/CallExprMatchRule.h"
#include "Rules/MemberAccessMatchRule.h"
#include "Rules/RefExprMatchRule.h"
#include "Rules/TypeNameMatchRule.h"

#include "DebugInfoAttacher.h"

using namespace CangjieMigrator;
using namespace CangjieMigrator::Rules;
using namespace CangjieMigrator::Util;

CangjieMigrator::DebugInfoAttacher::DebugInfoAttacher(Path file)
    : file(file), lines(Io::ReadAllLines(file)), infos(std::vector<std::vector<std::string>>(lines.size() + 1))
{}

bool CangjieMigrator::DebugInfoAttacher::Attach(int line, const std::string &info)
{
    if (line >= infos.size()) {
        return false;
    }
    infos[line].push_back(info);
    return true;
}

using Cangjie::AST::Node;
using Cangjie::AST::ASTKind;

std::vector<DebugInfoAttacher::SelfDefineFunc> DebugInfoAttacher::selfDefines = {
    [](const AttachFunc &attach, const Node &node) {
        if (node.astKind != ASTKind::IMPORT_SPEC) {
            return false;
        }
        const auto &importSpec = Cast<ASTKind::IMPORT_SPEC>(node);
        return attach(importSpec.content.ToString());
    },
    [](const AttachFunc &attach, const Node &node) {
        if (node.astKind != ASTKind::CALL_EXPR) {
            return false;
        }
        const auto &callExpr = Cast<ASTKind::CALL_EXPR>(node);
        auto info = CallExprMatchRule::ExtractInfo(callExpr, PositionRange(callExpr));
        if (!info) {
            return false;
        }
        return attach(info->ToString());
    },
    [](const AttachFunc &attach, const Node &node) {
        if (node.astKind != ASTKind::MEMBER_ACCESS) {
            return false;
        }
        const auto &memberAccess = Cast<ASTKind::MEMBER_ACCESS>(node);
        auto info = MemberAccessMatchRule::ExtractInfo(memberAccess, PositionRange(memberAccess));
        if (!info) {
            return false;
        }
        return attach(info->ToString());
    },
    [](const AttachFunc &attach, const Node &node) {
        if (node.astKind != ASTKind::REF_EXPR) {
            return false;
        }
        const auto &refExpr = Cast<ASTKind::REF_EXPR>(node);
        auto info = RefExprMatchRule::ExtractInfo(refExpr, PositionRange(refExpr));
        if (!info) {
            return false;
        }
        return attach(info->ToString());
    },
    [](const AttachFunc &attach, const Node &node) {
        auto info = TypeNameMatchRule::ExtractInfo(node, PositionRange(node));
        if (!info) {
            return false;
        }
        return attach(info->ToString());
    }};

static bool ValidPositionRange(const PositionRange &range)
{
    if (range.start.fileID == range.end.fileID &&
        range.start.line == range.end.line &&
        range.start.column > 0 &&
        range.end.column > range.start.column) {
        return true;
    }
    return false;
}

bool CangjieMigrator::DebugInfoAttacher::Attach(const Node &node, const PositionRange &range)
{
    bool success = false;
    int line;
    if (ValidPositionRange(range)) {
        line = range.start.line;
        auto begin = range.start.column - 1;
        auto end = range.end.column - 1;

        auto kind = Cangjie::AST::ASTKIND_TO_STRING_MAP[node.astKind];
        std::string s;
        if (node.TestAttr(Cangjie::AST::Attribute::MACRO_EXPANDED_NODE)) {
            s = std::string(end, '-');
        } else {
            s = std::string(end, '^');
        }
        if (begin >= kind.length()) {
            int start = begin - (int)kind.length();
            for (int i = 0; i < start; i++) {
                s[i] = ' ';
            }
            for (int i = 0; i < kind.length(); i++) {
                s[start + i] = kind[i];
            }
        } else {
            for (int i = 0; i < begin; i++) {
                s[i] = ' ';
            }
            s += kind;
        }
        success |= Attach(line, s);
    } else {
        line = range.end.line;
        if (node.TestAttr(Cangjie::AST::Attribute::MACRO_EXPANDED_NODE)) {
            success |= Attach(range.end.line, "MACRO: " + range.start.ToString() + "to" + range.end.ToString() +
                Cangjie::AST::ASTKIND_TO_STRING_MAP[node.astKind]);
        } else {
            success |= Attach(range.end.line, range.start.ToString() + "to" + range.end.ToString() +
                Cangjie::AST::ASTKIND_TO_STRING_MAP[node.astKind]);
        }
    }

    auto attach = [this, line](const std::string &info) { return Attach(line, info); };
    for (const auto &selfDefine : selfDefines) {
        success |= selfDefine(attach, node);
    }

    return success;
}

bool CangjieMigrator::DebugInfoAttacher::Generate() const
{
    return Generate(file.parent_path() / (file.filename().u8string() + ".attach.cj"));
}

static void PrintInfo(std::ostream &s, const std::vector<std::string> &infos)
{
    for (const auto &info : infos) {
        std::stringstream stream(info);
        std::string l;
        while (std::getline(stream, l)) {
            s << "//  " << l << std::endl;
        }
    }
}

bool CangjieMigrator::DebugInfoAttacher::Generate(const Path &target) const
{
    std::ofstream f(target);
    if (!f) {
        std::cerr << "Open write " << target << " failed" << std::endl;
        return false;
    }

    PrintInfo(f, infos[0]);
    for (int i = 0; i < lines.size(); ++i) {
        f << "    " << lines[i] << std::endl;
        PrintInfo(f, infos[i + 1]);
    }
    return true;
}
